﻿using System;
using System.Collections.Generic;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Core.Common.Services;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Behaviors;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Caches;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Data.Providers;
using Microsoft.Practices.Unity;
using ProductFeedbackE = JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common.Entities.ProductFeedback;

namespace JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Services
{

	/// <summary>
	/// An <see cref="IProductFeedbackRuntimeService"/> implementation that retrieves product feedback.
	/// </summary>
	public class ProductFeedbackRuntimeService : IProductFeedbackRuntimeService
	{

        #region Properties

        /// <summary>
        /// Gets or sets the dependency injection container to use to resolve dependencies.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <see cref="ProductFeedbackRuntimeService"/> uses the <see cref="IUnityContainer"/> to resolve
        /// behaviors by name.
        /// </para>
        /// </remarks>
        [Dependency]
        public IUnityContainer Container { get; set; }

		/// <summary>
		/// Gets or sets the product feedback provider to use to get product feedback from the
		/// database.
		/// </summary>
		/// <value>An <see cref="IProductFeedbackProvider"/> implementation.</value>
		[Dependency]
		public IProductFeedbackProvider ProductFeedbackProvider { get; set; }

		/// <summary>
		/// Gets or sets the product feedback cache to use to cache product feedback.
		/// </summary>
		[Dependency]
		public IProductFeedbackCache ProductFeedbackCache { get; set; }

		/// <summary>
		/// Gets or sets the exception handling service to use to handle exceptions.
		/// </summary>
		/// <value>An <see cref="IExceptionHandlingService"/> implementation.</value>
		[Dependency]
		public IExceptionHandlingService ExceptionHandlingService { get; set; }

		#endregion // Properties

		#region IProductFeedbackRuntimeService Members

		/// <summary>
		/// Gets a list of product feedback by product identifier.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose 
		/// product feedback is to be retieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the product whose product feedback is to be retrieved is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
		/// whose product feedback is to be retrieved.</param>
        /// <param name="selectionBehaviorName">The name of the selection behavior to use to select
        /// product feedback.</param>
        /// <returns>An <see cref="IList{T}"/> of <see cref="ProductFeedbackE"/>.</returns>
		public IList<ProductFeedbackE> GetProductFeedback(Guid tenantId, string catalogId, string productId, string selectionBehaviorName)
		{
			Dictionary<Guid, ProductFeedbackE> feedback;
            IProductFeedbackSelectionBehavior selectionBehavior;
            IList<ProductFeedbackE> selectedFeedback;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
            Safety.AssertArgumentNotNull("selectionBehaviorName", selectionBehaviorName);

			try
			{
				feedback = GetProductFeedbackWithCaching(tenantId, catalogId, productId);
                selectionBehavior = GetProductFeedbackSelectionBehavior(selectionBehaviorName);
                selectedFeedback = selectionBehavior.SelectProductFeedback(feedback);
                return selectedFeedback;
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		#endregion // IProductFeedbackRuntimeService Members

        #region Implementation

        private Dictionary<Guid, ProductFeedbackE> GetProductFeedbackWithCaching(Guid tenantId, string catalogId, string productId)
        {
            Dictionary<Guid, ProductFeedbackE> feedback;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

            feedback = ProductFeedbackCache.GetProductFeedback(tenantId, catalogId, productId);
            if (feedback == null)
            {
                feedback = ProductFeedbackProvider.GetProductFeedback(tenantId, catalogId, productId);
                ProductFeedbackCache.SetProductFeedback(tenantId, catalogId, productId, feedback);
            }
            return feedback;
        }

        private IProductFeedbackSelectionBehavior GetProductFeedbackSelectionBehavior(string selectionBehaviorName)
        {
            IProductFeedbackSelectionBehavior behavior;

            Safety.AssertArgumentNotNull("selectionBehaviorName", selectionBehaviorName);

            try
            {
                behavior = Container.Resolve<IProductFeedbackSelectionBehavior>(selectionBehaviorName);
            }
            catch (ResolutionFailedException)
            {
                // FIXME: log this condition - it's an important one - and then raise another exception to indicate the source of the failure
                // is the provided name, not an automatic resolution.
                throw;
            }

            return behavior;
        }

        #endregion // Implementation

    }

}
